/**
 *******************************************************************************
 * @file  usb_dev_msc_scsi.c
 * @brief This file provides all the USBD SCSI layer functions.
 @verbatim
   Change Logs:
   Date             Author          Notes
   2019-11-19       CDT             First version
 @endverbatim
 *******************************************************************************
 * Copyright (C) 2020, Huada Semiconductor Co., Ltd. All rights reserved.
 *
 * This software component is licensed by HDSC under BSD 3-Clause license
 * (the "License"); You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at:
 *                    opensource.org/licenses/BSD-3-Clause
 *
 *******************************************************************************
 */

/*******************************************************************************
                   Host/Device Data Transfer Matrix
--------------------------------------------------------------------------
       |                              HOST                               |
--------------------------------------------------------------------------                             
       |      |     Hn        |         Hi        |             Ho       |
        ------------------------------------------------------------------ 
       |  Dn  |   (1) Hn=Dn   |     (4) Hi>Dn     |          (9) Ho>Dn   |
        ------------------------------------------------------------------
       |      |               |     (5) Hi>Di     |                      |
DEVICE |  Di  |   (2) Hn<Di   |     (6) Hi=Di     |          (10)Ho<>Di  |
       |      |               |     (7) Hi<Di     |                      |
        ------------------------------------------------------------------                         
       |      |               |                   |          (11)Ho>Do   |
       |  Do  |   (3) Hn<Do   |     (8) Hi<>Do    |          (12)Ho=Do   |
       |      |               |                   |          (13)Ho<Do   |
--------------------------------------------------------------------------

Note:all the 'case x' means (x) in the table above, x=1-13
Host Expectation:
    Hn  Host expects no data transfers
    Hi  Host expects to receive data from the device
    Ho  Host expects to send data to the device
Device Intent
    Dn  Device intends to transfer no data
    Di  Device intends to send data to the host
    Do  Device intends to receive data from the host
 ******************************************************************************/


/*******************************************************************************
 * Include files
 ******************************************************************************/
#include "usb_dev_msc_bot.h"
#include "usb_dev_msc_scsi.h"
#include "usb_dev_msc_mem.h"
#include "usb_dev_msc_data.h"

SCSI_Sense_TypeDef SCSI_Sense[SENSE_LIST_DEEPTH];
uint8_t  SCSI_Sense_Head;
uint8_t  SCSI_Sense_Tail;
uint32_t SCSI_blk_size;
uint32_t SCSI_blk_nbr;
uint64_t SCSI_blk_addr;     
uint32_t SCSI_blk_len;
usb_core_instance *cdev;

/*******************************************************************************
 * Local function prototypes
 ******************************************************************************/
int8_t scsi_testunitready(uint8_t lun);
int8_t scsi_inquiry(uint8_t lun, uint8_t *params);
int8_t scsi_readformatcapacity(uint8_t lun);
int8_t scsi_readcapacity10(uint8_t lun);
int8_t scsi_requestsense(uint8_t *params);
int8_t scsi_startstopuint(void);
int8_t scsi_modesense6(uint8_t lun);
int8_t scsi_modesense10(void);
int8_t scsi_write10(uint8_t lun, uint8_t *params);
int8_t scsi_read10(uint8_t lun, uint8_t *params);
int8_t scsi_verify10(uint8_t lun, uint8_t *params);
int8_t scsi_addressrangecheck(uint32_t blk_offset, uint16_t blk_nbr);
int8_t scsi_processread(uint8_t lun);
int8_t scsi_processwrite(uint8_t lun);


/**
 *******************************************************************************
 ** \brief  Processes SCSI commands
 ** \param  pdev: device instance
 ** \param  lun: number of logical unit
 ** \param  params: command
 ** \retval status
 ******************************************************************************/
int8_t scsi_cmdprocess(usb_core_instance *pdev, uint8_t lun, uint8_t *params)
{
    int8_t Res;
    
    Res = (int8_t)0;
    cdev = pdev;

    if(params[0] == SCSI_TEST_UNIT_READY)
    {
        Res = scsi_testunitready(lun);
    }
    else if(params[0] == SCSI_REQUEST_SENSE)
    {
        Res = scsi_requestsense(params);
    }
    else if(params[0] == SCSI_INQUIRY)
    {
        Res = scsi_inquiry(lun, params);
    }
    else if(params[0] == SCSI_START_STOP_UNIT)
    {
        Res = scsi_startstopuint( );
    }
    else if(params[0] == SCSI_ALLOW_MEDIUM_REMOVAL)
    {
        Res = scsi_startstopuint( );
    }
    else if(params[0] == SCSI_MODE_SENSE6)
    {
        Res = scsi_modesense6(lun);
    }
    else if(params[0] == SCSI_MODE_SENSE10)
    {
        Res = scsi_modesense10( );
    }
    else if(params[0] == SCSI_READ_FORMAT_CAPACITIES)
    {
        Res = scsi_readformatcapacity(lun);
    }
    else if(params[0] == SCSI_READ_CAPACITY10)
    {
        Res = scsi_readcapacity10(lun);
    }
    else if(params[0] == SCSI_READ10)
    {
        Res = scsi_read10(lun, params);
    }
    else if(params[0] == SCSI_WRITE10)
    {
        Res = scsi_write10(lun, params);
    }
    else if(params[0] == SCSI_VERIFY10)
    {
        Res = scsi_verify10(lun, params);
    }
    else
    {
        scsi_sensecode(ILLEGAL_REQUEST, INVALID_CDB);
        Res = (int8_t)-1;
    }
    return Res;
}

/**
 *******************************************************************************
 ** \brief  Processes SCSI Test Unit Ready Command
 ** \param  lun: number of logical unit
 ** \retval status
 ******************************************************************************/
int8_t scsi_testunitready(uint8_t lun)
{
    int8_t i8Ret;
    i8Ret = (int8_t)0;
    // case 9 : Hi > D0
    if (MSC_BOT_cbw.dDataLength != 0u)
    {
        scsi_sensecode(ILLEGAL_REQUEST, INVALID_CDB);
        i8Ret = (int8_t)-1;
    }
    else
    {
        if (msc_fops->IsReady(lun) != 0u)
        {
            scsi_sensecode(NOT_READY, MEDIUM_NOT_PRESENT);
            i8Ret = (int8_t)-1;
        }
        else
        {
            MSC_BOT_DataLen = 0u;
        }
    }
    return i8Ret;
}

/**
 *******************************************************************************
 ** \brief  Processes inquiry command
 ** \param  lun: number of logical unit
 ** \param  params: command
 ** \retval status
 ******************************************************************************/
int8_t scsi_inquiry(uint8_t lun, uint8_t *params)
{
    const uint8_t* pPage;
    uint16_t len;

    if(params[1] & 0x01u)
    {
        pPage = (const uint8_t *)MSC_Page00_Inquiry_Data;
        len   = LENGTH_INQUIRY_PAGE00;
    }
    else
    {

        pPage = (uint8_t *)&msc_fops->pInquiry[lun * USB_DEV_INQUIRY_LENGTH];
        len   = (uint16_t)pPage[4] + (uint16_t)5;

        if(params[4] <= len)
        {
            len = params[4];
        }
    }
    MSC_BOT_DataLen = len;

    while(len)
    {
        len--;
        MSC_BOT_Data[len] = pPage[len];
    }
    return (int8_t)0;
}

/**
 *******************************************************************************
 ** \brief  Processes Read Capacity10 command
 ** \param  lun: number of logical unit
 ** \retval status
 ******************************************************************************/
int8_t scsi_readcapacity10(uint8_t lun)
{
    int8_t i8Ret = (int8_t)0;
    if(msc_fops->GetCapacity(lun, &SCSI_blk_nbr, &SCSI_blk_size) != 0u)
    {
        scsi_sensecode(NOT_READY, MEDIUM_NOT_PRESENT);
        i8Ret = (int8_t)-1;
    }
    else
    {
        MSC_BOT_Data[0] = (uint8_t)((SCSI_blk_nbr - 1u) >> 24u);
        MSC_BOT_Data[1] = (uint8_t)((SCSI_blk_nbr - 1u) >> 16u);
        MSC_BOT_Data[2] = (uint8_t)((SCSI_blk_nbr - 1u) >> 8u);
        MSC_BOT_Data[3] = (uint8_t)(SCSI_blk_nbr - 1u);

        MSC_BOT_Data[4] = (uint8_t)(SCSI_blk_size >> 24u);
        MSC_BOT_Data[5] = (uint8_t)(SCSI_blk_size >> 16u);
        MSC_BOT_Data[6] = (uint8_t)(SCSI_blk_size >> 8u);
        MSC_BOT_Data[7] = (uint8_t)(SCSI_blk_size);
        MSC_BOT_DataLen = 8u;
    }
    return i8Ret;
}

/**
 *******************************************************************************
 ** \brief  Processes Read Format Capacity command
 ** \param  lun: number of logical unit
 ** \retval status
 ******************************************************************************/
int8_t scsi_readformatcapacity(uint8_t lun)
{
    int8_t i8Ret = (int8_t)0;
    uint32_t blk_size;
    uint32_t blk_nbr;
    uint16_t i;

    i = 0;
    while(i<12)
    {
        MSC_BOT_Data[i++] = 0u;
    }

    if(msc_fops->GetCapacity(lun, &blk_nbr, &blk_size) != 0u)
    {
        scsi_sensecode(NOT_READY, MEDIUM_NOT_PRESENT);
        i8Ret = (int8_t)-1;
    }
    else
    {
        MSC_BOT_Data[3]  = 0x08u;
        MSC_BOT_Data[4]  = (uint8_t)((blk_nbr - 1u) >> 24u);
        MSC_BOT_Data[5]  = (uint8_t)((blk_nbr - 1u) >> 16u);
        MSC_BOT_Data[6]  = (uint8_t)((blk_nbr - 1u) >> 8u);
        MSC_BOT_Data[7]  = (uint8_t)(blk_nbr - 1u);

        MSC_BOT_Data[8]  = 0x02u;
        MSC_BOT_Data[9]  = (uint8_t)(blk_size >> 16u);
        MSC_BOT_Data[10] = (uint8_t)(blk_size >> 8u);
        MSC_BOT_Data[11] = (uint8_t)(blk_size);

        MSC_BOT_DataLen  = 12u;
    }
    return i8Ret;
}

/**
 *******************************************************************************
 ** \brief  Processes Mode Sense6 command
 ** \param  lun: number of logical unit
 ** \retval status
 ******************************************************************************/
int8_t scsi_modesense6(uint8_t lun)
{
    uint16_t len;
    len = 8u;
    MSC_BOT_DataLen = len;
    while(len)
    {
        len--;
        MSC_BOT_Data[len] = MSC_Mode_Sense6_data[len];
    }
    return (int8_t)0;
}

/**
 *******************************************************************************
 ** \brief  processes Mode Sense10 command
 ** \param  none
 ** \retval status
 ******************************************************************************/
int8_t scsi_modesense10(void)
{
    uint16_t len = 8u;

    MSC_BOT_DataLen = len;
    while(len)
    {
        len--;
        MSC_BOT_Data[len] = MSC_Mode_Sense10_data[len];
    }
    return (int8_t)0;
}

/**
 *******************************************************************************
 ** \brief  processes Request Sense command
 ** \param  params: command
 ** \retval status
 ******************************************************************************/
int8_t scsi_requestsense(uint8_t *params)
{
    uint8_t i;

    i = 0u;
    while(i < REQUEST_SENSE_DATA_LEN)
    {
        MSC_BOT_Data[i++] = 0u;
    }
    MSC_BOT_Data[0] = 0x70u;
    MSC_BOT_Data[7] = REQUEST_SENSE_DATA_LEN - 6u;
    if((SCSI_Sense_Head != SCSI_Sense_Tail))
    {
        MSC_BOT_Data[2]  = SCSI_Sense[SCSI_Sense_Head].Skey;
        MSC_BOT_Data[12] = SCSI_Sense[SCSI_Sense_Head].w.b.ASCQ;
        MSC_BOT_Data[13] = SCSI_Sense[SCSI_Sense_Head].w.b.ASC;
        SCSI_Sense_Head++;
        if(SCSI_Sense_Head == SENSE_LIST_DEEPTH)
        {
            SCSI_Sense_Head = 0u;
        }
    }
    MSC_BOT_DataLen = REQUEST_SENSE_DATA_LEN;
    if(params[4] <= REQUEST_SENSE_DATA_LEN)
    {
        MSC_BOT_DataLen = params[4];
    }
    return (int8_t)0;
}

/**
 *******************************************************************************
 ** \brief  Loads the last error code in the error list
 ** \param  sKey: Sense Key
 ** \param  ASC: Additional Sense Key
 ** \retval none
 ******************************************************************************/
void scsi_sensecode(uint8_t sKey, uint8_t ASC)
{
    SCSI_Sense[SCSI_Sense_Tail].Skey  = sKey;
    SCSI_Sense[SCSI_Sense_Tail].w.ASC = (uint32_t)ASC << 8u;
    SCSI_Sense_Tail++;
    if(SCSI_Sense_Tail == SENSE_LIST_DEEPTH)
    {
        SCSI_Sense_Tail = 0u;
    }
}

/**
 *******************************************************************************
 ** \brief  processes Start Stop Unit command
 ** \param  none
 ** \retval status
 ******************************************************************************/
int8_t scsi_startstopuint(void)
{
    MSC_BOT_DataLen = 0u;
    return (int8_t)0;
}

/**
 *******************************************************************************
 ** \brief  processes Read10 command
 ** \param  lun: number of logical unit
 ** \param  params: command
 ** \retval status
 ******************************************************************************/
int8_t scsi_read10(uint8_t lun, uint8_t *params)
{
    int8_t i8Ret = (int8_t)0;
    if(MSC_BOT_State == BOT_IDLE) 
    {
        // case 10 : Ho <> Di 
        if((MSC_BOT_cbw.bmFlags & 0x80u) != 0x80u)
        {
            scsi_sensecode(ILLEGAL_REQUEST, INVALID_CDB);
            i8Ret = (int8_t)-1;
        }
        else if(msc_fops->IsReady(lun) != 0u)
        {
            scsi_sensecode(NOT_READY, MEDIUM_NOT_PRESENT);
            i8Ret = (int8_t)-1;
        }
        else
        {
            SCSI_blk_addr = ((uint64_t)params[2] << 24u) | \
                            ((uint64_t)params[3] << 16u) | \
                            ((uint64_t)params[4] << 8u) | \
                            (uint64_t)params[5];

            SCSI_blk_len = ((uint32_t)params[7] << 8u) | \
                           (uint32_t)params[8];


            if(scsi_addressrangecheck((uint32_t)SCSI_blk_addr, (uint16_t)SCSI_blk_len) < 0)
            {
                i8Ret = (int8_t)-1;             // error 
            }
            else
            {
                MSC_BOT_State  = BOT_DATA_IN;
                SCSI_blk_addr *= SCSI_blk_size;
                SCSI_blk_len  *= SCSI_blk_size;
                // cases 4,5 : Hi <> Dn 
                if(MSC_BOT_cbw.dDataLength != SCSI_blk_len)
                {
                    scsi_sensecode(ILLEGAL_REQUEST, INVALID_CDB);
                    i8Ret = (int8_t)-1;
                }
            }
        }
    }
    if(((int8_t)-1) != i8Ret)
    {
        MSC_BOT_DataLen = (uint16_t)MSC_MEDIA_PACKET;
        i8Ret = scsi_processread(lun);
    }
    return i8Ret;
}

/**
 *******************************************************************************
 ** \brief  processes Write10 command
 ** \param  lun: number of logical unit
 ** \param  params: command
 ** \retval status
 ******************************************************************************/
int8_t scsi_write10(uint8_t lun, uint8_t *params)
{
    int8_t i8Ret = (int8_t)0;
    if(MSC_BOT_State == BOT_IDLE) 
    {
        // case 8 : Hi <> Do 
        if((MSC_BOT_cbw.bmFlags & 0x80u) == 0x80u)
        {
            scsi_sensecode(ILLEGAL_REQUEST, INVALID_CDB);
            i8Ret = (int8_t)-1;
        }
        else
        {
            if(msc_fops->IsReady(lun) != 0u)
            {
                scsi_sensecode(NOT_READY, MEDIUM_NOT_PRESENT);
                i8Ret = (int8_t)-1;
            }
            // Check If media is write-protected
            else if(msc_fops->IsWriteProtected(lun) != 0u)
            {
                scsi_sensecode(NOT_READY, WRITE_PROTECTED);
                i8Ret = (int8_t)-1;
            }
            else
            {
                SCSI_blk_addr = ((uint64_t)params[2] << 24u) | \
                                ((uint64_t)params[3] << 16u) | \
                                ((uint64_t)params[4] << 8u) | \
                                (uint64_t)params[5];
                SCSI_blk_len  = ((uint32_t)params[7] << 8u) | \
                                (uint32_t)params[8];
                // check if LBA address is in the right range 
                if(scsi_addressrangecheck((uint32_t)SCSI_blk_addr, (uint16_t)SCSI_blk_len) < 0)
                {
                    i8Ret = (int8_t)-1;             // error 
                }
                else
                {
                    SCSI_blk_addr *= SCSI_blk_size;
                    SCSI_blk_len  *= SCSI_blk_size;
                    // cases 3,11,13 : Hn,Ho <> D0 
                    if(MSC_BOT_cbw.dDataLength != SCSI_blk_len)
                    {
                        scsi_sensecode(ILLEGAL_REQUEST, INVALID_CDB);
                        i8Ret = (int8_t)-1;
                    }
                    else
                    {
                        // Prepare EP to receive first data packet
                        MSC_BOT_State = BOT_DATA_OUT;
                        hd_usb_readytorx(cdev, MSC_OUT_EP, MSC_BOT_Data, (uint16_t)MIN(SCSI_blk_len, MSC_MEDIA_PACKET));
                    }
                }
            }
        }
    }
    else // Write Process ongoing 
    {
        i8Ret = scsi_processwrite(lun);
    }
    return i8Ret;
}

/**
 *******************************************************************************
 ** \brief  processes Verify10 command
 ** \param  lun: number of logical unit
 ** \param  params: command
 ** \retval status
 ******************************************************************************/
int8_t scsi_verify10(uint8_t lun, uint8_t *params)
{
    int8_t i8Ret;
    
    i8Ret = (int8_t)0;
    if((params[1] & 0x02u) == 0x02u)
    {
        scsi_sensecode(ILLEGAL_REQUEST, INVALID_FIELED_IN_COMMAND);
        i8Ret = (int8_t)-1;                 // Error, Verify Mode Not supported
    }
    else if(scsi_addressrangecheck((uint32_t)SCSI_blk_addr, (uint16_t)SCSI_blk_len) < (int8_t)0)
    {
        i8Ret = (int8_t)-1;                 // error
    }
    else
    {
        MSC_BOT_DataLen = 0u;
    }
    return i8Ret;
}

/**
 *******************************************************************************
 ** \brief  checks the address range
 ** \param  blk_offset: first block address
 ** \param  blk_nbr: number of block to be processed
 ** \retval status
 ******************************************************************************/
int8_t scsi_addressrangecheck(uint32_t blk_offset, uint16_t blk_nbr)
{
    int8_t i8Ret = (int8_t)0;
    if((blk_offset + blk_nbr) > SCSI_blk_nbr)
    {
        scsi_sensecode(ILLEGAL_REQUEST, ADDRESS_OUT_OF_RANGE);
        i8Ret = (int8_t)-1;
    }
    return i8Ret;
}

/**
 *******************************************************************************
 ** \brief  handles Read Process
 ** \param  lun: number of logical unit
 ** \retval status
 ******************************************************************************/
int8_t scsi_processread(uint8_t lun)
{
    uint32_t len;
    int8_t i8Ret = (int8_t)0;

    len = MIN(SCSI_blk_len, MSC_MEDIA_PACKET);

    if(SCSI_blk_size == 0u)    
    {
        i8Ret = (int8_t)-1;
    }
    else if(msc_fops->Read(lun, MSC_BOT_Data, SCSI_blk_addr / SCSI_blk_size, len / SCSI_blk_size) < 0)
    {
        scsi_sensecode(HARDWARE_ERROR, UNRECOVERED_READ_ERROR);
        i8Ret = (int8_t)-1;
    }
    else
    {
        hd_usb_deveptx(cdev, MSC_IN_EP, MSC_BOT_Data, len);
        SCSI_blk_addr            += len;
        SCSI_blk_len             -= len;
        // case 6 : Hi = Di
        MSC_BOT_csw.dDataResidue -= len;
        if(SCSI_blk_len == 0u)
        {
            MSC_BOT_State = BOT_LAST_DATA_IN;
        }
    }
    return i8Ret;
}

/**
 *******************************************************************************
 ** \brief  handles Write Process
 ** \param  lun: number of logical unit
 ** \retval status
 ******************************************************************************/
int8_t scsi_processwrite(uint8_t lun)
{
    uint32_t len;
    int8_t i8Ret = (int8_t)0;

    len = MIN(SCSI_blk_len, MSC_MEDIA_PACKET);

    if(SCSI_blk_size == 0u)
    {
        i8Ret = (int8_t)-1;
    }
    else if(msc_fops->Write(lun, MSC_BOT_Data, SCSI_blk_addr / SCSI_blk_size, len / SCSI_blk_size) < 0)
    {
        scsi_sensecode(HARDWARE_ERROR, WRITE_FAULT);
        i8Ret = (int8_t)-1;
    }
    else
    {
        SCSI_blk_addr            += len;
        SCSI_blk_len             -= len;
        // case 12 : Ho = Do
        MSC_BOT_csw.dDataResidue -= len;
        if(SCSI_blk_len == 0u)
        {
            msc_bot_txcsw(cdev, CSW_CMD_PASSED);
        }
        else
        {
            // Prapare EP to Receive next packet
            hd_usb_readytorx(cdev, MSC_OUT_EP, MSC_BOT_Data, (uint16_t)MIN(SCSI_blk_len, MSC_MEDIA_PACKET));
        }
    }
    return i8Ret;
}

/*******************************************************************************
 * EOF (not truncated)
 ******************************************************************************/
